<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Drag and drop</title>

    <style>
        #from, #to {
            width: 205px;
            height: 35px;
            border: 1px solid black;
            margin: 20px;
        }

        #to-display-values {
            width: 500px;
            height: 200px;
            border: 1px solid black;
            margin: 20px;
        }

        #draggable {
            width: 100px;
            height: 35px;
            background-color: red;
            display: inline-block;

        }
    </style>
</head>
<body>
<div id="from">
    <div id="parent" draggable="true">
        <div id="draggable"></div>
    </div>
</div>
<div id="to"></div>

<br/><br/><br/>
<a id="link" href="http://example.com">Link</a>
<div id="to-display-values"></div>
<img id="img" src="./img.png"/>

<script>
    {
        const from            = document.querySelector('#from');
        const to              = document.querySelector('#to');
        const toDisplayValues = document.querySelector('#to-display-values');
        const draggable       = document.querySelector('#draggable');

        from.addEventListener('dragstart', function (e) {
            e.dataTransfer.setData('text', e.target.children[0].id);
        });

        to.addEventListener('dragover', function (e) {
            e.preventDefault();
        });

        toDisplayValues.addEventListener('dragover', function (e) {
            e.dataTransfer.setData('custom/type', 'customValue');

            if (e.dataTransfer.getData('custom/type'))
                throw new Error("It should not be allowed to change dataTransfer data after dragstart event");

            e.preventDefault();
        });

        to.addEventListener('drop', function (e) {
            e.preventDefault();
            e.target.appendChild(document.getElementById(e.dataTransfer.getData('text')));
        });

        toDisplayValues.addEventListener('drop', function (e) {
            e.preventDefault();

            const values = [];

            for (let i = 0; i < e.dataTransfer.types.length; i++)
                values.push(e.dataTransfer.getData(e.dataTransfer.types[i]));

            e.target.textContent = values.join(' - ');
        });

        // NOTE: the exact order depends on random factors (like cursor moving speed and time).
        // Here is defined required events that should be raised in a certain order (some additional
        // events can be raise between these events ('eventName-elementSelector' format is used).
        window.requiredEvents = [
            'mousedown-#draggable',
            'mousemove-#draggable',
            'dragstart-#parent',
            'drag-#parent',
            'dragenter-#draggable',
            'dragover-#draggable',
            'drag-#parent',
            'dragover-#draggable',
            'drag-#parent',
            'dragleave-#draggable',
            'drag-#parent',
            'dragenter-#to',
            'dragleave-body',
            'dragover-#to',
            'drag-#parent',
            'dragover-#to',
            'drop-#to',
            'dragend-#parent'
        ];
        window.raisedEvents   = [];

        function waitForNextEvent (index) {
            let nextItemStr = window.requiredEvents[index];

            if (!nextItemStr)
                return;

            nextItem = nextItemStr.split('-');

            const eventName = nextItem[0];
            const selector  = nextItem[1];

            function eventHandler () {
                document.querySelector(selector).removeEventListener(eventName, eventHandler);
                window.raisedEvents.push(nextItemStr);
                waitForNextEvent(index + 1);
            }

            document.querySelector(selector).addEventListener(eventName, eventHandler);
        }

        waitForNextEvent(0);

        document.onclick = function () {
            throw new Error('Click is raised but should not');
        };
    }
</script>
</body>
</html>
